﻿using System;
using System.Drawing;
using System.Windows.Forms;
using DirectShowLib;
using DirectShowLib.Utils;
using MediaFoundation.EVR;
using Microsoft.Win32;

namespace MediaPlayer
{
    public partial class Player : UserControl
    {
        static public Graph Graph;

        #region Properties

        private bool stretch = true;
        public bool Stretch { get { return this.stretch; } set { this.stretch = value; SetVideoPosition(); } }

        private double ar;
        public double AspectRatio { get { return this.ar; } set { this.ar = value; SetVideoPosition(); } }

        public bool DisableScreensaver { get; set; }

        public string File { get; internal set; }
        public TimeSpan? Length { get; internal set; }
        public MediaType Type { get; internal set; }

        public RenderMode Renderer { get; set; }

        private PlayState state = PlayState.NoFile;
        public PlayState State
        {
            get
            {
                return this.state;
            }
            internal set
            {
                if (this.state == value) return;

                if (StateChanged != null) StateChanged(this, this.state, value);

                this.state = value;
            }
        }

        public double Speed
        {
            get
            {
                double rate = 1;

                if (Graph.Seeking != null)
                {
                    Graph.Seeking.GetRate(out rate);
                }

                return rate;
            }
            set
            {
                if (Graph.Seeking != null) Graph.Seeking.SetRate(value);
            }
        }

        public double Position
        {
            get
            {
                if (Graph.Position != null)
                {
                    double position;
                    Graph.Position.get_CurrentPosition(out position);
                    return position;
                }

                return 0;
            }
            set
            {
                if (Graph.Position != null)
                {
                    Graph.Position.put_CurrentPosition((double)value);
                }
            }
        }
        public Size? Resolution { get; internal set; }
        private int volume = 50;
        public int Volume
        {
            get
            {
                return this.volume;
            }
            set
            {
                this.volume = value;

                if (value > 100)
                {
                    this.volume = 100;
                }
                else if (value < 0)
                {
                    this.volume = 0;
                }

                if (Muted) return;

                if (Graph.Audio != null)
                {
                    Graph.Audio.put_Volume(convertVolumeDb(this.volume));
                }
            }
        }

        public bool Muted { get; internal set; }
        public void Mute(bool? mute = null)
        {
            if (mute == null)
            {
                mute = !this.Muted;
            }

            if (mute.Value)
            {
                this.Muted = true;

                if (Graph.Audio != null)
                {
                    Graph.Audio.put_Volume(convertVolumeDb(0));
                }
            }
            else
            {
                this.Muted = false;

                if (Graph.Audio != null)
                {
                    Graph.Audio.put_Volume(convertVolumeDb(this.volume));
                }
            }
        }

        public MediaFile MediaFile;

        #endregion

        public Player()
        {
            InitializeComponent();

            Graph = new Graph(this.panelWindow.Handle);

            this.Renderer = RenderMode.VMR7;
            this.Speed = 1.0;
            this.Volume = 100;
            this.DisableScreensaver = true;
            this.AspectRatio = MediaPlayer.AspectRatio.AROriginal;

            this.MouseWheel += new MouseEventHandler(Player_MouseWheel);
            SystemEvents.DisplaySettingsChanged += new EventHandler(SystemEvents_DisplaySettingsChanged); // WM_DISPLAYCHANGE
        }

        private void Player_Load(object sender, EventArgs e)
        {

        }

        #region Events

        public event StateChangedHandler StateChanged;
        public delegate void StateChangedHandler(Player p, PlayState oldState, PlayState newState);

        public event MouseMoveHandler MouseMove;
        public delegate void MouseMoveHandler(Player p, MouseEventArgs e);

        public event DoubleClickHandler DoubleClick;
        public delegate void DoubleClickHandler(Player p, EventArgs e);

        public event RightClickHandler RightClick;
        public delegate void RightClickHandler(Player p, EventArgs e);

        public event MouseScrollHandler MouseScroll;
        public delegate void MouseScrollHandler(Player p, MouseEventArgs e);

        #endregion

        #region Event Handlers

        protected override void WndProc(ref Message m)
        {
            switch (m.Msg)
            {
                case 0x0400 + 13: // WM_GRAPHNOTIFY
                    {
                        HandleGraphEvent();
                        break;
                    }
                case 0x0200: //0x0200 WM_MOUSEMOVE
                    {
                        // Fire event
                        if (MouseMove != null)
                        {
                            MouseMove(this, new MouseEventArgs(MouseButtons.None, 0, NativeMethods.GET_X_LPARAM(m.LParam), NativeMethods.GET_Y_LPARAM(m.LParam), 0));
                        }
                        break;
                    }
                case 0x0203: //0x0203 WM_LBUTTONDBLCLK (0x0201 WM_LBUTTONDOWN, 0x0202 WM_LBUTTONUP)
                    {
                        // Fire event
                        if (DoubleClick != null)
                        {
                            DoubleClick(this, null);
                        }
                        break;
                    }
                case 0x0205: //0x0205 WM_RBUTTONUP (0x0204 WM_RBUTTONDOWN, 0x0206 WM_RBUTTONDBLCLK)
                    {
                        // Fire event
                        if (RightClick != null)
                        {
                            RightClick(this, null);
                        }
                        break;
                    }
                case 0x020A: //0x020A WM_MOUSEWHEEL
                    {
                        // Fire event
                        if (MouseScroll != null)
                        {
                            MouseButtons buttons = NativeMethods.GetButtons(m.WParam);
                            int x = NativeMethods.GET_X_LPARAM(m.LParam);
                            int y = NativeMethods.GET_Y_LPARAM(m.LParam);
                            int delta = NativeMethods.GET_WHEEL_DELTA_WPARAM(m.WParam);

                            MouseScroll(this, new MouseEventArgs(buttons, 0, x, y, delta));
                        }
                        break;
                    }
            }

            if (Graph != null && Graph.Window != null)
            {
                Graph.Window.NotifyOwnerMessage(m.HWnd, m.Msg, m.WParam, m.LParam);
            }

            base.WndProc(ref m);
        }

        private void HandleGraphEvent()
        {
            if (Graph.Events == null) return;

            EventCode evCode;
            IntPtr evParam1, evParam2;

            // Process all queued events
            while (Graph.Events.GetEvent(out evCode, out evParam1, out evParam2, 0) == 0)
            {
                // Free memory associated with callback
                Graph.Events.FreeEventParams(evCode, evParam1, evParam2);

                // If this is the end of the clip, stop
                if (evCode == EventCode.Complete)
                {
                    Stop();
                }
            }
        }

        private void Player_Resize(object sender, EventArgs e)
        {
            SetVideoPosition();
        }

        private void Player_Move(object sender, EventArgs e)
        {
            SetVideoPosition();
        }

        private void SetVideoPosition()
        {
            if (Graph.Renderer != null)
            {
                WindowPosition(this.Stretch);
                Graph.Renderer.SetVideoPosition(SourceRectangle(), DsRect.FromRectangle(this.panelWindow.ClientRectangle));
            }
        }

        private MFVideoNormalizedRect SourceRectangle()
        {
            //int targetWidth = this.Resolution.Value.Height; // 1:1

            //float onePx = (float)1 / (float)this.Resolution.Value.Width;
            //float difference = (((float)this.Resolution.Value.Width - (float)targetWidth) * (float)onePx) / 2f;
            //float targetRight = 1f - difference;
            //float targetLeft = difference;

            //this.panelWindow.Width = targetWidth;
            //this.panelWindow.Height = this.Resolution.Value.Height;
            //this.panelWindow.Location = new Point((this.Width - this.panelWindow.Width) / 2, (this.Height - this.panelWindow.Height) / 2);

            //return new MFVideoNormalizedRect(targetLeft, 0, targetRight, 1);

            return new MFVideoNormalizedRect(0, 0, 1, 1);
        }


        private void WindowPosition(bool stretch)
        {
            if (this.Resolution == null) return;

            if (this.AspectRatio == -1 || this.AspectRatio == 0.0) this.AspectRatio = (double)this.Resolution.Value.Width / this.Resolution.Value.Height;

            if (stretch)
            {
                if (((double)this.Size.Width / this.Size.Height) > this.AspectRatio)
                {
                    this.panelWindow.Height = this.Height;
                    this.panelWindow.Width = (int)(this.Height * this.AspectRatio);

                    this.panelWindow.Location = new Point((this.Width - this.panelWindow.Width) / 2, 0);
                }
                else
                {
                    this.panelWindow.Width = this.Width;
                    this.panelWindow.Height = (int)(this.Width * (1 / this.AspectRatio));

                    this.panelWindow.Location = new Point(0, (this.Height - this.panelWindow.Height) / 2);
                }
            }
            else
            {
                if (((double)this.Size.Width / this.Size.Height) > this.AspectRatio)
                {
                    this.panelWindow.Size = new Size((int)(this.Resolution.Value.Height * this.AspectRatio), this.Resolution.Value.Height);
                    this.panelWindow.Location = new Point((int)((this.ClientRectangle.Width - (this.Resolution.Value.Height * this.AspectRatio)) / 2), (int)((this.ClientRectangle.Height - this.Resolution.Value.Height) / 2));
                }
                else
                {
                    this.panelWindow.Size = new Size(this.Resolution.Value.Height, (int)(this.Resolution.Value.Width * (1 / this.AspectRatio)));
                    this.panelWindow.Location = new Point((int)((this.ClientRectangle.Width - this.Resolution.Value.Width) / 2), (int)((this.ClientRectangle.Height - (this.Resolution.Value.Width * (1 / this.AspectRatio))) / 2));
                }
            }
        }

        private void Player_Paint(object sender, PaintEventArgs e)
        {
            // WTF is going on?

            //if (Graph.Renderer != null)
            //{
            //IntPtr hdc = e.Graphics.GetHdc();
            //IntPtr hdc = this.panelWindow.CreateGraphics().GetHdc();
            //Graph.Renderer.Repaint(hdc);
            // e.Graphics.ReleaseHdc(hdc);
            //}

            //e.Graphics.FillRectangle(new SolidBrush(Color.Black), this.ClientRectangle);
        }

        void Player_MouseWheel(object sender, MouseEventArgs e)
        {
            // Fire Event
            if (MouseScroll != null)
            {
                MouseScroll(this, e);
            }
        }

        private void SystemEvents_DisplaySettingsChanged(object sender, EventArgs e)
        {
            if (Graph.Renderer != null)
            {
                Graph.Renderer.DisplayModeChanged();
            }
        }

        private void panelWindow_MouseMove(object sender, MouseEventArgs e)
        {
            // Fire event
            if (MouseMove != null)
            {
                MouseMove(this, new MouseEventArgs(MouseButtons.None, 0, e.X, e.Y, 0));
            }
        }

        private void panelWindow_MouseDoubleClick(object sender, MouseEventArgs e)
        {
            // Fire event
            if (DoubleClick != null)
            {
                DoubleClick(this, null);
            }
        }

        #endregion

        private void SetupRenderer()
        {
            if (this.Type != MediaType.AudioVideo) return;

            CloseRenderer();

            switch (this.Renderer)
            {
                case RenderMode.EVR:
                    Graph.Renderer = new Renderers.EVR(this.panelWindow.Handle);
                    break;
                case RenderMode.VMR9:
                    Graph.Renderer = new Renderers.VMR9(this.panelWindow.Handle);
                    break;
                case RenderMode.VMR7:
                default:
                    Graph.Renderer = new Renderers.VMR7(this.panelWindow.Handle);
                    break;
            }
        }

        private void CloseRenderer()
        {
            if (Graph.Renderer != null) Graph.Renderer.Dispose();
            Graph.Renderer = null;
        }

        public bool LoadFile(string file)
        {
            if (!System.IO.File.Exists(file)) return false;

            Graph.RemoveAllFilters();
            CloseRenderer();

            this.File = file;

            MediaFile info = new MediaFile(this.File);
            this.MediaFile = info;

            if (info.Video.Count == 0)
            {
                this.Type = MediaType.Audio;
            }
            else
            {
                this.Type = MediaType.AudioVideo;

                this.Resolution = new Size(info.Video[0].Width, info.Video[0].Height);
            }

            this.Length = info.Duration;

            //OABool seek;
            //int hr = Graph.Position.CanSeekForward(out seek);
            //DsError.ThrowExceptionForHR(hr);

            if (Graph.Renderer == null && this.Type == MediaType.AudioVideo)
            {
                SetupRenderer();
            }

            // Force render redraw
            SetVideoPosition();

            try
            {
                Graph.RenderFile(file);
            }
            catch
            {
                return false;
            }

            this.State = PlayState.Ready;

            return true;
        }

        public void Play()
        {
            if (this.DisableScreensaver) NativeMethods.DisableScreensaver();

            if (Graph.Renderer != null) this.panelWindow.Show();

            int hr = Graph.Control.Run();
            DsError.ThrowExceptionForHR(hr);

            // Debug
            Graph.GetFilters();

            this.State = PlayState.Playing;
        }

        public void Pause()
        {
            if (Graph.Control.Pause() >= 0) this.State = PlayState.Paused;
        }

        public void Stop()
        {
            if (this.DisableScreensaver) NativeMethods.EnableScreensaver();

            if ((Graph.Control == null) || (Graph.Seeking == null)) return;

            // Stop and reset position to beginning
            if ((this.State == PlayState.Paused) || (this.State == PlayState.Playing))
            {
                Graph.Control.Stop();

                this.State = PlayState.Stopped;

                // Seek to the beginning
                Graph.Seeking.SetPositions(new DsLong(0), AMSeekingSeekingFlags.AbsolutePositioning, null, AMSeekingSeekingFlags.NoPositioning);
            }

            if (Graph.Renderer != null) this.panelWindow.Hide();
        }

        public void Close()
        {
            if (Graph.Control != null) Graph.Control.Stop();
            if (Graph != null) Graph.Dispose();
        }

        public void ScaleVideo(double multiplier)
        {
            // Check there is video
            if (this.Resolution == null) return;

            int hr = Graph.Window.SetWindowPosition(0, 0, Convert.ToInt32(this.Resolution.Value.Width * multiplier), Convert.ToInt32(this.Resolution.Value.Height * multiplier));
            DsError.ThrowExceptionForHR(hr);
        }

        private void ScaleVideoExact(int width, int height)
        {
            if (this.Resolution == null) return;

            int hr = Graph.Window.SetWindowPosition(0, 0, width, height);
            DsError.ThrowExceptionForHR(hr);
        }

        public void ScaleVideoFit()
        {
            if (this.Resolution == null) return;

            double videoRes = (double)this.Resolution.Value.Width / (double)this.Resolution.Value.Height;
            double frameRes = (double)this.Width / (double)this.Height;

            int width = 0;
            int height = 0;
            int top = 0;
            int left = 0;
            int hr = 0;

            if (true) // Program.Settings.VideoScale
            {
                this.Parent.MinimumSize = new System.Drawing.Size(100, 50);
                if (videoRes > frameRes)
                {
                    width = this.Width;
                    height = (int)Math.Floor(width / videoRes);
                    top = (this.Height - height) / 2;
                }
                else
                {
                    height = this.Height;
                    width = (int)Math.Floor(height * videoRes);
                    left = (this.Width - width) / 2;
                }

                hr = Graph.Window.SetWindowPosition(left, top, width, height);
            }
            else
            {
                this.Parent.MinimumSize = (this.Dock == DockStyle.Fill) ? new System.Drawing.Size(this.Resolution.Value.Width, this.Resolution.Value.Height) : new System.Drawing.Size(this.Resolution.Value.Width + 16, this.Resolution.Value.Height + 160);
                left = (this.Width - this.Resolution.Value.Width) / 2;
                top = (this.Height - this.Resolution.Value.Height) / 2;
                hr = Graph.Window.SetWindowPosition(left, top, this.Resolution.Value.Width, this.Resolution.Value.Height);
            }

            DsError.ThrowExceptionForHR(hr);
        }

        private int convertVolumeDb(int percent)
        {
            double db = -100;

            percent = (percent > 100) ? 100 : percent;

            if (percent > 0)
            {
                double pct = (double)percent / 100;
                double log = Math.Log10(pct);
                db = 20 * log;
            }

            return (int)(db * 100);
        }

        public static bool RendererAvalible(RenderMode renderer)
        {
            bool avalible = false;

            switch (renderer)
            {
                case RenderMode.VMR7:
                    avalible = FilterGraphTools.IsVMR7Present();
                    break;
                case RenderMode.VMR9:
                    avalible = FilterGraphTools.IsVMR9Present();
                    break;
                case RenderMode.EVR:
                    avalible = FilterGraphTools.IsThisComObjectInstalled(typeof(EnhancedVideoRenderer).GUID);
                    break;
            }

            return avalible;
        }
    }
}
